package edu.gatech.aristotle.random.ineqalities;

import edu.gatech.aristotle.random.util.CONSTANT_EXPERIMENT;
import net.util.Debug;

/***
 * Compute the chernoff bound with real expectation (e.g., passed test cases in the universe).
 * 
 * @author wli7
 *
 */
public class ChernoffBound {

	double deviation;
	int passedNumber, sampleSize;
	
	double randomSampleRatio;
	
	public ChernoffBound(){}
	
	public ChernoffBound( double aDeviation, int aPassedNumber, int aSampleSize ){
		this.deviation		= aDeviation;
		this.passedNumber	= aPassedNumber;
		this.sampleSize		= aSampleSize;
		
		this.randomSampleRatio = (double)aPassedNumber / aSampleSize;
		
		//Debug.println( "Sample ratio: " + this.randomSampleRatio );
	}
	
	public String makeBatchReport(){
		StringBuffer report	= new StringBuffer();
		
		for ( int loop_d = 0; loop_d < CONSTANT_EXPERIMENT.DEVIATIONS.length; loop_d++ ) {
			report.append( "\n****************************************************************\n\n" );
			
			for ( int loop_p = 0; loop_p < CONSTANT_EXPERIMENT.PASSED_RATES.length; loop_p++ ){
				
				for ( int randomSize = CONSTANT_EXPERIMENT.RANDOM_SIZE_LOWER; 
						randomSize <= CONSTANT_EXPERIMENT.RANDOM_SIZE_UPPER; 
				)
				{
					
					ChernoffBound	chernoffBound	
										= new ChernoffBound( CONSTANT_EXPERIMENT.DEVIATIONS[loop_d],
															(int)(CONSTANT_EXPERIMENT.PASSED_RATES[loop_p] * randomSize),
															randomSize );

					String singleReport	= chernoffBound.makeReportOneLine();//chernoffBound.makeReport();
					report.append( singleReport );
					
					randomSize +=  CONSTANT_EXPERIMENT.RANDOM_SIZE_INTERVAL;
				}
				
				report.append( "\n****************************************************************\n\n" );
			}

		}
		
		return report.toString();
	}
	
	public String makeReportOneLine(){
		StringBuffer	report	= new StringBuffer();
		
		report.append( this.deviation + "\t");
		report.append( this.randomSampleRatio + "\t");
		report.append( this.sampleSize + "\t");
		report.append( ( 1 - this.doUpperConfidenceBound() ) + "\t");
		report.append( "\n" );
		
		Debug.println( report.toString() );
		return report.toString();
		
	}
	
	public String makeReport(){

		StringBuffer report	= new StringBuffer();
		
		report.append( this.makeParameterReport() );
		report.append( this.makeEstimationReport() );
		report.append( this.makeConfidenceBoundReport() );
		
//		this.makeParameterReport();
//		this.makePredictionReport();
//		this.makeErrorReport();
//		
		return report.toString();
	}
	
	public String makeParameterReport(){
		StringBuffer report	= new StringBuffer();
		report.append( "=====Parameter Report=====\n" );
		String columnName	= "Deviation, passed number, sample size, passed rate\n";
		String value		= this.deviation + ",\t" + this.passedNumber + ",\t" + this.sampleSize + "\t" + this.randomSampleRatio+ "\n";
		
		report.append( columnName );
		report.append( value );
		
		//Debug.println( report.toString() );

		return report.toString();
	}
	
	public String makeConfidenceBoundReport(){
		StringBuffer report	= new StringBuffer();
		report.append( "=====Confidence Bound Report=====\n" );
		report.append( "Upper confidence: \t" + doUpperConfidenceBound() + "\n" );
		report.append( "Lower confidence: \t" + doLowerConfidenceBound() + "\n" );
		
		//Debug.println( report );	
		
		return report.toString();
	}

	public String makeEstimationReport(){
		StringBuffer report	= new StringBuffer();
		report.append( "=====Estimation Report=====\n" );
		report.append( "Estimation: \t\nupper:\t" + doUpperEstimation() + 
						"\nlower:\t" + doLowerEstimation() + "\n" );
		
		//Debug.println( report );
		return report.toString();
	}
	
	public double doUpperEstimation(){
		double upperBound	= randomSampleRatio * (1/(1-this.deviation));
		
		if ( upperBound > 1 ) upperBound = 1;
		
		return upperBound;
	}
	
	public double doLowerEstimation(){
		double lowerPrediction	= randomSampleRatio * (1/(1+this.deviation));
		
		return lowerPrediction;
	}
	
	public double doLowerConfidenceBound(){
		double lowerTail		= Math.pow( Math.E, -(this.deviation * this.deviation * this.passedNumber) / 2 );
		
		return 1 - lowerTail;
	}
	
	public double doUpperConfidenceBound(){
		double upperTail	= Math.pow( Math.E, -(this.deviation * this.deviation * this.passedNumber) / 3 );
		
		return 1 - upperTail;
	}
}
